package embuzzled.g5;

import java.awt.Color;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Random;

public class Helper 
{
	public static final int COLOR_LIMIT = 50;
	
	/**
	 * Static instance of the random class.
	 * All nondeterministic functions will use rand.
	 * To use a seed, set SEED to the desired seed value.
	 */
	public static Random rand;
	
	public static void initializeRandom(long seed)
	{
		//if(useSeed)
		//{
			//System.out.println("Random number generator using seed " + seed);
			rand = new Random(seed);
		//}
			
		/*else if(!isRandGenerated)
		{
			rand = new Random();
			long randomSeed = rand.nextLong();
			System.out.println("Random number generator using seed " + randomSeed);
			rand = new Random(seed);
		}*/
			
	}	
	
	/**
	 *  Creates a new copy of a boolean array.
	 * @param a A 2d boolean array
	 * @return A new copy of a.
	 */
	public static boolean[][] copyArray(boolean[][] a)
	{
		boolean[][] copy = new boolean[a.length][a[0].length];
		
		for(int i = 0; i < a.length; i++)
			for(int j = 0; j < a[0].length; j++)
				copy[i][j] = a[i][j];
		
		return copy;
	}
	
	/**
	 *  Creates a new copy of a Color array.
	 * @param a A 2d Color array
	 * @return A new copy of a.
	 */
	public static Color[][] copyArray(Color[][] a)
	{
		Color[][] copy = new Color[a.length][a[0].length];
		
		for(int i = 0; i < a.length; i++)
			for(int j = 0; j < a[0].length; j++)
				copy[i][j] = a[i][j];
		
		return copy;
	}
	
	
	
	/**
	 * Attempt to place color map "part" onto color map "whole" at point p.
	 * Returns the combined color map if possible, null if not.
	 * @param whole
	 * @param part
	 * @param p
	 * @return
	 */
	public static Color[][] superimpose(Color[][] whole, Color[][] part, Point p)
	{
		int x = p.x;
		int y = p.y;
		
		int localRows = part.length;
		int localCols = part[0].length;
		
		int xMax = p.x + localCols;
		int yMax = p.y + localRows;
		
		/*System.out.println("Adding a " +  localCols + "x" + localRows +
				" puzzle to a " + whole[0].length + "x" + whole.length +
				" map at location (" + x + ", " + y + ")");*/
		
		//Sanity check. Part would fall off the edge of whole.
		if(xMax > whole[0].length)
			return null;
		if(yMax > whole.length)
			return null;
		
		Color[][] newMap = new Color[whole.length][whole[0].length];
		
		for(int r = 0; r < localRows; r++)
			for(int c = 0; c < localCols; c++)
			{
				int wholeR = r + p.y;
				int wholeC = c + p.x;
				
				//System.out.println("Checking (" + wholeR + ", " + wholeC + ")");
				
				boolean isWholeClear = whole[wholeR][wholeC].equals(Constants.COLOR_CLEAR);
				boolean isPartClear = part[r][c].equals(Constants.COLOR_CLEAR);
				
				if(isPartClear)
				{
					//Not coloring this cell, keep old color.
					//System.out.println("Don't need to color");
					newMap[wholeR][wholeC] = whole[wholeR][wholeC];
				}
				else if(isWholeClear)
				{
					//Color in a previously clear cell.
					//System.out.println("Color in cell using " + part[r][c]);
					if(checkColorNeighborsEqual(whole, part[r][c], r, c, 1)) //Make sure not place similar colors next to each other.
					{
						//System.out.println("too similar adding in " + r + ", " + c);
						return null;
					}
						
					newMap[wholeR][wholeC] = part[r][c];
				}
				else if(!isWholeClear)
				{
					//Attempting to recolor an already colored cell.
					return null;
				}
			}
		
		//Copy the rest of the previous color map.
		for(int r = 0; r < newMap.length; r++)
			for(int c = 0; c < newMap[0].length; c++)
			{
				if(newMap[r][c] == null)
					newMap[r][c] = whole[r][c];
			}
		
		return newMap;
	}
	
	public static Color[][] superimposeWithBackground(Color[][] puzzleMap) {
		
		return superimposeWithBackground(puzzleMap, 1);
		
	}
	
	public static Color[][] superimposeWithBackground(Color[][] puzzleMap, int colorRadius) {
		
		int rows = puzzleMap.length;
		int cols = puzzleMap[0].length;
		
		Random rand = Helper.rand;
		
		Color[][] puzzleMapWithBG = puzzleMap;
		
		for (int row = 0; row < rows; row++) {
			
			for (int col = 0; col < cols; col++) {
		
				if (puzzleMapWithBG[row][col].equals(Constants.COLOR_CLEAR)) {
					
					boolean hasGotColored = false; // a condition for how many times to pick a different color
					int colorTries = 0;
					
					while (!hasGotColored) {
						//System.out.println("Coloring cell [" + row + ", " + col + "]");
						// get random color
						int rnum = rand.nextInt(Constants.ColorSelection.length);
						Color c = Constants.ColorSelection[rnum];
						
						//Taking too long, might stall. Pick random color.
						if(colorTries > COLOR_LIMIT)
							c = new Color(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256));
						
						// check if cells around have the same color
						boolean isSameColor = checkColorNeighbors(puzzleMap, c, row, col, colorRadius);
						
						if (!isSameColor) {
							
							puzzleMapWithBG[row][col] = c;
							
							hasGotColored = true;
						}
						
						colorTries++;
						
					}
					
				}
				
			}
			
		}
		
		return puzzleMapWithBG;
		
	}
	
	public static boolean checkColorNeighbors(Color[][] map, Color color, int row, int col, int radius) {
		
		int maxRow = map.length - 1;
		int maxCol = map[0].length - 1;

		int y[] = { -1, -1, -1, 0, 0, 1, 1, 1 };
		int x[] = { -1, 0, 1, -1, 1, -1, 0, 1 };

		int dy[]; // modified based on radius
		int dx[]; // modified based on radius
		
		// add neighbors
		if (radius > 1) {

			dy = new int[radius * y.length];
			dx = new int[radius * x.length];	
			
			int k = 1; // radius count
			int dj = 0; //  array count
			
			while (k <= radius) {
				
				int j = 0;
				
				while (j < x.length) {

					dy[dj] = k * y[j];
					dx[dj] = k * x[j];
					
					dj++;
					j++;
				}
				
				k++;
			}	
			
		} else {
			
			dx = x;
			dy = y;
		}
		
		/*
		System.out.print("dx: ");
		for (int m = 0; m<dx.length; m++) {
			System.out.print(dx[m] + " ");
		}

		System.out.print("dy: ");
		for (int m = 0; m<dy.length; m++) {
			System.out.print(dy[m] + " ");
		}
		*/
		
		// check through neighbors
		for (int i = 0; i < dx.length; i++) {
			 
			int nr = row + dy[i];
			int nc = col + dx[i];
			
			if (nr >= 0 && nc >=0 && nr <= maxRow && nc <= maxCol) {

				//System.out.println(" (" + row + "," + col + ") " + color + " vs. (" + nr + "," + nc + ") " + map[nr][nc]);
				
				if (map[nr][nc].equals(color)) {
					return true;
					
				} else if (isSimilarColor(map[nr][nc], color)) {
					return true;
				}
			}
			
		}
		
		// none of above conditions were met
		return false;
		
	}
	
	/**
	 * Checks if a color would be equal to the cells around it if placed in given row and column
	 * @param map The color map
	 * @param color A color to place
	 * @param row The row to place the color
	 * @param col The column to place the color
	 * @param radius How close can this color be to equal colors before we worry?
	 * @return True if there is at least one color in the radius around (row, col) equal to the given color.
	 *         False otherwise.
	 */
public static boolean checkColorNeighborsEqual(Color[][] map, Color color, int row, int col, int radius) {
		
		int maxRow = map.length - 1;
		int maxCol = map[0].length - 1;

		int y[] = { -1, -1, -1, 0, 0, 1, 1, 1 };
		int x[] = { -1, 0, 1, -1, 1, -1, 0, 1 };
		
		int dy[]; // modified based on radius
		int dx[]; // modified based on radius
		
		// add neighbors
		if (radius > 1) {

			dy = new int[radius * y.length];
			dx = new int[radius * x.length];	
			
			int k = 1; // radius count
			int dj = 0; //  array count
			
			while (k <= radius) {
				
				int j = 0;
				
				while (j < x.length) {

					dy[dj] = k * y[j];
					dx[dj] = k * x[j];
					
					dj++;
					j++;
				}
				
				k++;
			}	
			
		} else {
			
			dx = x;
			dy = y;
		}
		
		for (int i = 0; i < dx.length; i++) {
			 
			int nr = row + dy[i];
			int nc = col + dx[i];
			
			if (nr >= 0 && nc >=0 && nr <= maxRow && nc <= maxCol)
				if (map[nr][nc].equals(color)) {
					return true;
					
				}
		}
		
		// none of above conditions were met
		return false;
		
	}
	
	public static boolean isSimilarColor(Color mc, Color c) {
		
		// check if two of three rgb colors are similar
		int similarRGBrange = 0;
		int range = 50;
		
		int similarRGBdeeprange = 0;
		int deeprange = 80;
		
		if (Math.abs(mc.getBlue() - c.getBlue()) < range)
			similarRGBrange++;
		if (Math.abs(mc.getRed() - c.getRed()) < range)
			similarRGBrange++;
		if (Math.abs(mc.getGreen() - c.getGreen()) < range)
			similarRGBrange++;
		
		// return if similarity is met
		if (similarRGBrange > 1) return true;
		
		if (mc.getBlue() == c.getBlue()) {
			if (Math.abs(mc.getRed() - c.getRed()) < deeprange) 
				similarRGBdeeprange++;
			else if (Math.abs(mc.getGreen() - c.getGreen()) < deeprange) 
				similarRGBdeeprange++;
		} else if (mc.getGreen() == c.getGreen()) {
			if (Math.abs(mc.getRed() - c.getRed()) < deeprange) 
				similarRGBdeeprange++;
			else if (Math.abs(mc.getBlue() - c.getBlue()) < deeprange) 
				similarRGBdeeprange++;
		} else if (mc.getRed() == c.getRed()) {
			if (Math.abs(mc.getGreen() - c.getGreen()) < deeprange) 
				similarRGBdeeprange++;
			else if (Math.abs(mc.getBlue() - c.getBlue()) < deeprange) 
				similarRGBdeeprange++;
		}
		
		// return if similarity is met
		if (similarRGBdeeprange > 0) return true;
		
		// no similarity found
		return false;
		
	}
	
	/**
	 *  Randomly generates a permutation of the given array a
	 *  by swapping elements a.size() times.
	 *  Non destructive
	 * @param a An array of integers
	 * @return A random permutation of a.
	 */
	public static int[] permuteArray(int[] a)
	{
		Random r = Helper.rand;
		int n = a.length;
		
		int[] permute = new int[n];
		
		for(int i = 0; i < n; i++)
			permute[i] = a[i];
		
		int i1, i2;
		
		for(int i = 0; i < n; i++)
		{
			//Pick two indices to swap
			i1 = r.nextInt(n);
			i2 = r.nextInt(n);
			
			//Swap the elements in the indices
			int temp = permute[i1];
			permute[i1] = permute[i2];
			permute[i2] = temp;
		}
		
		return permute;
	}
	
	public static Color[] permuteArray(Color[] a)
	{
		Random r = Helper.rand;
		int n = a.length;
		
		Color[] permute = new Color[n];
		
		for(int i = 0; i < n; i++)
			permute[i] = a[i];
		
		int i1, i2;
		
		for(int i = 0; i < n; i++)
		{
			//Pick two indices to swap
			i1 = r.nextInt(n);
			i2 = r.nextInt(n);
			
			//Swap the elements in the indices
			Color temp = permute[i1];
			permute[i1] = permute[i2];
			permute[i2] = temp;
		}
		
		return permute;
	}
	
}
